﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using SharpPcap;
using SharpPcap.Packets;
using Hids.Enums;
using Hids.Classes.AttackSignatures;

namespace Hids.Classes
{
    /// <summary>
    /// This class analyzes incoming packets and looks for attack signatures
    /// </summary>
    public class NetworkAnalyzer
    {
        #region Private Properties

        #region Maimon Scan Properties

        /// <summary>
        /// Holds a list of the times when Maimon packets were recieved
        /// </summary>
        private static Dictionary<string, List<DateTime>> _maimonPacketTimesByIpAddress = new Dictionary<string, List<DateTime>>();

        /// <summary>
        /// The time of the last Maimon Scan was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastMaimonScanDetectedByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// Holds the number of Fin Packets in an Maimon Scan
        /// </summary>
        private const int _numberOfPacketsToAMaimonScan = 20;

        /// <summary>
        /// How long to wait before reseting the Maimon packet counter
        /// </summary>
        private readonly static TimeSpan _timeBeforeResetingMaimonScanCounter = new TimeSpan(0, 3, 0); //3 minutes

        /// <summary>
        /// How long to wait after an Maimon Scan has been detected before considering it a new scan
        /// </summary>
        private readonly static TimeSpan _waitTimeBeforeANewMaimonScan = new TimeSpan(0, 5, 0);//5 minutes

        #endregion

        #region Fin Scan Properties

        /// <summary>
        /// Holds a list of the times when fin packets were recieved
        /// </summary>
        private static Dictionary<string, List<DateTime>> _finPacketTimesByIpAddress = new Dictionary<string, List<DateTime>>();

        /// <summary>
        /// The time of the last Fin Scan was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastFinScanDetectedByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// Holds the number of Fin Packets in an Fin Scan
        /// </summary>
        private const int _numberOfPacketsToAnFinScan = 20;

        /// <summary>
        /// How long to wait before reseting the Fin packet counter
        /// </summary>
        private readonly static TimeSpan _timeBeforeResetingFinScanCounter = new TimeSpan(0, 3, 0); //3 minutes

        /// <summary>
        /// How long to wait after an Fin Scan has been detected before considering it a new scan
        /// </summary>
        private readonly static TimeSpan _waitTimeBeforeANewFinScan = new TimeSpan(0, 5, 0);//5 minutes

        #endregion

        #region Xmas Scan Properties

        /// <summary>
        /// Holds a list of the times when xmas packets were recieved
        /// </summary>
        private static Dictionary<string, List<DateTime>> _xmasPacketTimesByIpAddress = new Dictionary<string, List<DateTime>>();

        /// <summary>
        /// The time of the last Xmas Scan was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastXmasScanDetectedByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// Holds the number of Xmas Packets in an Xmas Scan
        /// </summary>
        private const int _numberOfPacketsToAnXmsScan = 10;

        /// <summary>
        /// How long to wait before reseting the xmas packet counter
        /// </summary>
        private readonly static TimeSpan _timeBeforeResetingXmasScanCounter = new TimeSpan(1, 0, 0); //1 hour

        /// <summary>
        /// How long to wait after an Xmas Scan has been detected before considering it a new scan
        /// </summary>
        private readonly static TimeSpan _waitTimeBeforeANewXmasScan = new TimeSpan(0, 5, 0);//5 minutes

        #endregion

        #region Null Scan Properties

        /// <summary>
        /// Holds a list of the times when Null packets were recieved
        /// </summary>
        private static Dictionary<string, List<DateTime>> _nullPacketTimesByIpAddress = new Dictionary<string, List<DateTime>>();

        /// <summary>
        /// The time of the las Null Scan was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastNullScanDetectedByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// Holds the number of Null Packets in an Xmas Scan
        /// </summary>
        private const int _numberOfPacketsToAnNullScan = 10;

        /// <summary>
        /// How long to wait before reseting the null packet counter
        /// </summary>
        private readonly static TimeSpan _timeBeforeResetingNullScanCounter = new TimeSpan(1, 0, 0); //1 hour

        /// <summary>
        /// How long to wait after an Null Scan has been detected before considering it a new scan
        /// </summary>
        private readonly static TimeSpan _waitTimeBeforeANewNullScan = new TimeSpan(0, 5, 0);//5 minutes

        #endregion

        #region SYN Scan Properties

        /// <summary>
        /// if true, then a SYN packet has been recieved at the specified ip address and port number, without an RST or ACK packet being recieved
        /// </summary>
        /// <example>if _synHalfOpenConnectionsByPortAndIpAddress[192.168.2.2][53] is null, then 192.168.2.2 
        /// has sent a SYN packet to 53 and successfully open or closed the connection.
        /// if _synHalfOpenConnectionsByPortAndIpAddress[192.168.2.2][53] is not null, then it is the packet that half opened the connection with
        /// 192.168.2.2 at port 53
        /// </example>
        private static Dictionary<string, Dictionary<int, Packet>> _synHalfOpenConnectionsByPortAndIpAddress = new Dictionary<string, Dictionary<int, Packet>>();

        /// <summary>
        /// Holds the number of SYN packets that have been recieved followed immediately by an RST packet by ip address
        /// </summary>
        /// <example>if _numberOfSynThenRstPackets[192.168.2.2] is 5, then 192.168.2.2 has sent 5 SYN packets and then immediately closed 
        /// them by sending RST packets
        /// </example>
        private static Dictionary<string, int> _numberOfSynThenRstPacketsByIpAddress = new Dictionary<string, int>();

        /// <summary>
        /// The time of the last Syn Packet immediately followed by an RST packet was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastHalfOpenConnectionByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// The time of the last Syn Scan was detected for a specific IP Address
        /// </summary>
        private static Dictionary<string, DateTime> _lastSynScanDetectedByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// Holds the number of Syn Packets immediately followed by an RST packet
        /// and the number of unopened connections started with a SYN packet that are in an Syn Scan
        /// </summary>
        private const int _numberOfUnopenedSynPacketsToASynScan = 10;

        /// <summary>
        /// The number of milliseconds to wait before considering a half open connection to be a syn scan attempt
        /// </summary>
        private readonly static TimeSpan _timeBeforeClosingHalfOpenConnection = new TimeSpan(0, 0, 15);//10 seconds

        /// <summary>
        /// How long to wait before reseting the Syn packet counter
        /// </summary>
        private readonly static TimeSpan _timeBeforeResetingSynScanCounter = new TimeSpan(1, 0, 0); //1 hour

        /// <summary>
        /// How long to wait after a Syn Scan has been detected before considering it a new scan
        /// </summary>
        private readonly static TimeSpan _waitTimeBeforeANewSynScan = new TimeSpan(0, 30, 0);//30 minutes

        #endregion

        #region Land Attack Properties

        /// <summary>
        /// The amount of time before sending a second LAND Attack Alert
        /// </summary>
        private static TimeSpan _timeBetweenLandAttackAlerts = new TimeSpan(0, 10, 0);//10 minutes

        /// <summary>
        /// The last time a LAND attack was detected
        /// </summary>
        private static DateTime _lastLandAttackDetected = new DateTime(1, 1, 1);

        #endregion

        #region Smurf Attack Properties

        /// <summary>
        /// Holds the time an IP address sent a ping reply packet.  Used to help to determine Smurf Attacks
        /// </summary>
        private static Dictionary<string, DateTime> _lastPingReplyTimeByIpAddress = new Dictionary<string, DateTime>();

        /// <summary>
        /// The amount of time before reseting the ping reply packet count, i.e. each reply that is stored in 
        /// _lastPingReplyTimeByIpAddress is within this amount of time
        /// </summary>
        private static TimeSpan _timeBeforeResetingPingReplies = new TimeSpan(0, 2, 0);//2 minutes

        /// <summary>
        /// The number of ping Replies from different sources within the _timeBeforeResetingPingReplies that are in a Smurf Attack
        /// </summary>
        private static int _numberOFPingRepliesToASmurfAttack = 50;

        /// <summary>
        /// The amount of time before sending a second Smurf Attack alert
        /// </summary>
        private static TimeSpan _timeBetweenSmurfAlerts = new TimeSpan(0, 10, 0); //10 minutes

        /// <summary>
        /// The last time a smurf attack was detected.  
        /// </summary>
        private static DateTime _lastSmurfAttackDetected = new DateTime(1, 1, 1);

        #endregion

        #region Ping Of Death Attack Properties

        /// <summary>
        /// The amount of time before sending a second Smurf Attack alert
        /// </summary>
        private static TimeSpan _timeBetweenPingOfDeathAlerts = new TimeSpan(0, 10, 0); //10 minutes

        /// <summary>
        /// The last time a ping of death attack was detected from an IP address
        /// </summary>
        private static Dictionary<string, DateTime> _lastPingOfDeathAlertByIpAddress = new Dictionary<string, DateTime>();

        #endregion

        #region DNS Amplification Attack Properties

        /// <summary>
        /// The amount of time before sending a second Smurf Attack alert
        /// </summary>
        private static TimeSpan _timeBetweenDnsAmplificationAlerts = new TimeSpan(0, 10, 0); //10 minutes

        /// <summary>
        /// The last time a DNS Amplification attack was detected.  
        /// </summary>
        private static DateTime _lastDnsAmplificationAttackDetected = new DateTime(1, 1, 1);

        /// <summary>
        /// The number of DNS replies that consitutes a DNS Amplification attack when found within the time limit
        /// </summary>
        /// <example>The current settings detect an attack when 50 DNS replies are detected within 10 seconds</example>
        private static int _numberOfDnsRepliesToADnsAmplificationAttack = 50; 

        /// <summary>
        /// If the number of DNS replies are found within this time limit, send an alert
        /// </summary>
        private static TimeSpan _timeLimitOnDnsReplies = new TimeSpan(0, 0, 10); //10 seconds

        /// <summary>
        /// Holds a list of the DNS Reply times from the last 
        /// </summary>
        private static List<DateTime> _dnsReplyTimes = new List<DateTime>();

        #endregion

        /// <summary>
        /// Holds all the attack signatures to detect
        /// </summary>
        private static List<AttackSignature> _attacksToDetect = new List<AttackSignature>();

        #endregion

        #region Private Methods

        /// <summary>
        /// Checks the packets recieved for an Maimon Scan attempt, should be called with each new packet
        /// It Looks for packets with just the FIN/ACK flags set
        /// </summary>
        /// <param name="newPacket">The new packet just recieved from the network device</param>
        /// <returns>Whether or not an Maimon Scan was detected</returns>
        private static bool DetectMaimonScan(Packet newPacket)
        {
            TCPPacket tcpPacket = newPacket as TCPPacket;
            if (tcpPacket != null && tcpPacket.Fin && tcpPacket.Ack && !tcpPacket.Psh
                && !tcpPacket.Urg && !tcpPacket.Syn && !tcpPacket.Rst)//if the packet is a TCP Packet 
            {                                                       //and the just the ACK/FIN flag is set, it's most likely an Maimon Scan
                Logs.WriteMessageToLog("Maimon Packet Recieved");//log current activity

                string sourceIpAddress = tcpPacket.ipv4.SourceAddress.ToString();//get the ip address of the packet
                if (!_maimonPacketTimesByIpAddress.ContainsKey(sourceIpAddress))
                    _maimonPacketTimesByIpAddress.Add(sourceIpAddress, new List<DateTime>()); //if this is the first Maimon packet from the ip address create a new list
                _maimonPacketTimesByIpAddress[sourceIpAddress].Add(tcpPacket.PcapHeader.Date);//add the packet to the list of Maimon packets

                if (!_lastMaimonScanDetectedByIpAddress.ContainsKey(sourceIpAddress))//set the last time of a detected Maimon Scan
                    _lastMaimonScanDetectedByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));//if it has not been set before

                #region Remove old Maimon packets

                List<DateTime> packetTimesToRemove = new List<DateTime>();
                foreach (DateTime packetTime in _maimonPacketTimesByIpAddress[sourceIpAddress])
                {
                    if (packetTime.Add(_timeBeforeResetingMaimonScanCounter) < tcpPacket.PcapHeader.Date)
                        packetTimesToRemove.Add(packetTime);
                }
                foreach (DateTime packetTime in packetTimesToRemove)
                    _maimonPacketTimesByIpAddress[sourceIpAddress].Remove(packetTime);

                #endregion

                if (_maimonPacketTimesByIpAddress[sourceIpAddress].Count >= _numberOfPacketsToAMaimonScan &&
                    tcpPacket.PcapHeader.Date.Subtract(_waitTimeBeforeANewMaimonScan) >= _lastMaimonScanDetectedByIpAddress[sourceIpAddress])
                {
                    _lastMaimonScanDetectedByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                    SendAlertsAndWriteLog("Possible Maimon Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + tcpPacket.PcapHeader.Date.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks the packets recieved for an Fin Scan attempt, should be called with each new packet
        /// It Looks for packets with just the FIN flag set
        /// </summary>
        /// <param name="newPacket">The new packet just recieved from the network device</param>
        /// <returns>Whether or not an Fin Scan was detected</returns>
        private static bool DetectFinScan(Packet newPacket)
        {
            TCPPacket tcpPacket = newPacket as TCPPacket;
            if (tcpPacket != null && tcpPacket.Fin && !tcpPacket.Urg && !tcpPacket.Psh 
                && !tcpPacket.Ack && !tcpPacket.Syn && !tcpPacket.Rst)//if the packet is a TCP Packet 
            {                                                       //and the just the FIN flag is set, it's most likely an Fin Scan
                Logs.WriteMessageToLog("Fin Packet Recieved");//log current activity

                string sourceIpAddress = tcpPacket.ipv4.SourceAddress.ToString();//get the ip address of the packet
                if (!_finPacketTimesByIpAddress.ContainsKey(sourceIpAddress))
                    _finPacketTimesByIpAddress.Add(sourceIpAddress, new List<DateTime>()); //if this is the first fin packet from the ip address create a new list
                _finPacketTimesByIpAddress[sourceIpAddress].Add(tcpPacket.PcapHeader.Date);//add the packet to the list of fin packets

                if (!_lastFinScanDetectedByIpAddress.ContainsKey(sourceIpAddress))//set the last time of a detected fin Scan
                    _lastFinScanDetectedByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));//if it has not been set before

                #region Remove old fin packets

                List<DateTime> packetTimesToRemove = new List<DateTime>();
                foreach (DateTime packetTime in _finPacketTimesByIpAddress[sourceIpAddress])
                {
                    if (packetTime.Add(_timeBeforeResetingFinScanCounter) < tcpPacket.PcapHeader.Date)
                        packetTimesToRemove.Add(packetTime);
                }
                foreach (DateTime packetTime in packetTimesToRemove)
                    _finPacketTimesByIpAddress[sourceIpAddress].Remove(packetTime);

                #endregion

                if (_finPacketTimesByIpAddress[sourceIpAddress].Count >= _numberOfPacketsToAnFinScan &&
                    tcpPacket.PcapHeader.Date.Subtract(_waitTimeBeforeANewFinScan) >= _lastFinScanDetectedByIpAddress[sourceIpAddress])
                {
                    _lastFinScanDetectedByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                    SendAlertsAndWriteLog("Possible Fin Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + tcpPacket.PcapHeader.Date.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks the packets recieved for an Xmas Scan attempt, should be called with each new packet
        /// It looks for packets with FIN,URG, and PSH set
        /// </summary>
        /// <param name="newPacket">The new packet just recieved from the network device</param>
        /// <returns>Whether or not an Xmas Scan was detected</returns>
        private static bool DetectXmasScan(Packet newPacket)
        {
            TCPPacket tcpPacket = newPacket as TCPPacket;
            if (tcpPacket != null && tcpPacket.Fin && tcpPacket.Urg && tcpPacket.Psh 
                && !tcpPacket.Ack && !tcpPacket.Syn && !tcpPacket.Rst)//if the packet is a TCP Packet 
            {                                                       //and the FIN,URG,and PSH flags are set, it's most likely an Xmas Scan
                Logs.WriteMessageToLog("Xmas Packet Recieved");//log current activity

                string sourceIpAddress = tcpPacket.ipv4.SourceAddress.ToString();//get the ip address of the packet
                if (!_xmasPacketTimesByIpAddress.ContainsKey(sourceIpAddress))
                    _xmasPacketTimesByIpAddress.Add(sourceIpAddress, new List<DateTime>()); //if this is the first xmas packet from the ip address create a new list
                _xmasPacketTimesByIpAddress[sourceIpAddress].Add(tcpPacket.PcapHeader.Date);//add the packet to the list of xmas packets

                if (!_lastXmasScanDetectedByIpAddress.ContainsKey(sourceIpAddress))//set the last time of a detected xmas Scan
                    _lastXmasScanDetectedByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));//if it has not been set before

                #region Remove old xmas packets

                List<DateTime> packetTimesToRemove = new List<DateTime>();
                foreach (DateTime packetTime in _xmasPacketTimesByIpAddress[sourceIpAddress])
                {
                    if (packetTime.Add(_timeBeforeResetingXmasScanCounter) < tcpPacket.PcapHeader.Date)
                        packetTimesToRemove.Add(packetTime);
                }
                foreach (DateTime packetTime in packetTimesToRemove)
                    _xmasPacketTimesByIpAddress[sourceIpAddress].Remove(packetTime);

                #endregion

                if (_xmasPacketTimesByIpAddress[sourceIpAddress].Count >= _numberOfPacketsToAnXmsScan &&
                    tcpPacket.PcapHeader.Date.Subtract(_waitTimeBeforeANewXmasScan) >= _lastXmasScanDetectedByIpAddress[sourceIpAddress])
                {
                    _lastXmasScanDetectedByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                    SendAlertsAndWriteLog("Xmas Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + tcpPacket.PcapHeader.Date.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks the packets recieved for a Null Scan attempt, should be called with each new packet
        /// It looks for packets with no flags set
        /// </summary>
        /// <param name="newPacket">The new packet just recieved from the network device</param>
        /// <returns>Whether or not a Null Scan was detected</returns>
        private static bool DetectNullScan(Packet newPacket)
        {
            TCPPacket tcpPacket = newPacket as TCPPacket;
            if (tcpPacket != null && !tcpPacket.Urg && !tcpPacket.Ack && !tcpPacket.Psh 
                && !tcpPacket.Rst && !tcpPacket.Syn && !tcpPacket.Fin)//if the packet is a TCP Packet 
            {                                                       //has no flags set, it's most likely a null scan
                Logs.WriteMessageToLog("Null Packet Recieved");//log current activity

                string sourceIpAddress = tcpPacket.ipv4.SourceAddress.ToString();//get the ip address of the packet
                if (!_nullPacketTimesByIpAddress.ContainsKey(sourceIpAddress))
                    _nullPacketTimesByIpAddress.Add(sourceIpAddress, new List<DateTime>()); //if this is the first null packet from the ip address create a new list
                _nullPacketTimesByIpAddress[sourceIpAddress].Add(tcpPacket.PcapHeader.Date);//add the packet to the list of null packets

                if (!_lastNullScanDetectedByIpAddress.ContainsKey(sourceIpAddress))//set the last time of a detected Null Scan
                    _lastNullScanDetectedByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));//if it has not been set before

                #region Remove old null packets

                List<DateTime> packetTimesToRemove = new List<DateTime>();
                foreach (DateTime packetTime in _nullPacketTimesByIpAddress[sourceIpAddress])
                {
                    if (packetTime.Add(_timeBeforeResetingNullScanCounter) < tcpPacket.PcapHeader.Date)
                        packetTimesToRemove.Add(packetTime);
                }
                foreach (DateTime packetTime in packetTimesToRemove)
                    _nullPacketTimesByIpAddress[sourceIpAddress].Remove(packetTime);

                #endregion

                if (_nullPacketTimesByIpAddress[sourceIpAddress].Count >= _numberOfPacketsToAnNullScan &&
                    tcpPacket.PcapHeader.Date.Subtract(_waitTimeBeforeANewNullScan) >= _lastNullScanDetectedByIpAddress[sourceIpAddress])
                {
                    _lastNullScanDetectedByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                    SendAlertsAndWriteLog("Null Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + tcpPacket.PcapHeader.Date.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks the packets recieved for an SYN Scan attempt, should be called with each new packet
        /// it checks for the RST packets right after a SYN packet
        /// It is required to call LookForUnansweredSynConnections every so often to look for timeouts on SYN connections
        /// </summary>
        /// <param name="newPacket">The new packet just recieved from the network device</param>
        /// <returns>Whether or not an SYN Scan was detected</returns>
        private static bool DetectSynScan(Packet newPacket)
        {
            lock (_synHalfOpenConnectionsByPortAndIpAddress)//ensure that the packet_arrival event thread has exclusive access to this Dictionary
            {
                TCPPacket tcpPacket = newPacket as TCPPacket;
                if (tcpPacket != null && tcpPacket.SourceAddress.ToString() != Program.IpAddress)//if the packet is a TCP Packet and we
                {                                                                               //and its not a potential LAND attack
                    string sourceIpAddress = tcpPacket.ipv4.SourceAddress.ToString();//get the ip address of the packet
                    int destinationPortNumber = tcpPacket.DestinationPort;

                    #region Initalize Dictionaries(if they aren't initialized already)

                    if (!_lastHalfOpenConnectionByIpAddress.ContainsKey(sourceIpAddress))
                        _lastHalfOpenConnectionByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));

                    if (!_lastSynScanDetectedByIpAddress.ContainsKey(sourceIpAddress))
                        _lastSynScanDetectedByIpAddress.Add(sourceIpAddress, new DateTime(1, 1, 1));

                    if (!_numberOfSynThenRstPacketsByIpAddress.ContainsKey(sourceIpAddress))
                        _numberOfSynThenRstPacketsByIpAddress.Add(sourceIpAddress, 0);

                    if (!_synHalfOpenConnectionsByPortAndIpAddress.ContainsKey(sourceIpAddress)) //if this is the first packet from the ip address
                        _synHalfOpenConnectionsByPortAndIpAddress.Add(sourceIpAddress, new Dictionary<int, Packet>());//initialize the dictionary
                    if (!_synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].ContainsKey(destinationPortNumber))
                        _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].Add(destinationPortNumber, null);

                    #endregion

                    if (tcpPacket.PcapHeader.Date > _lastSynScanDetectedByIpAddress[sourceIpAddress].Add(_waitTimeBeforeANewSynScan))
                    {
                        if (tcpPacket.PcapHeader.Date > _lastHalfOpenConnectionByIpAddress[sourceIpAddress].Add(_timeBeforeResetingSynScanCounter))
                            _numberOfSynThenRstPacketsByIpAddress[sourceIpAddress] = 0;

                        if (tcpPacket.Syn && !tcpPacket.Ack)//if the packet is a SYN packet
                            _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber] = tcpPacket;//record the half open connection
                        else if (tcpPacket.Ack) //if the packet is an ACK packet, then the connection has been opened, and therefor isn't a SYN scan
                            _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber] = null; //record the open connection
                        else if (tcpPacket.Rst && _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber] != null)//if the packet has the RST flag set
                        {//and the connection was half open, then this packet was immediately opened and then closed, indicating a potential SYN scan
                            _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber] = null; //record the closed connection
                            _numberOfSynThenRstPacketsByIpAddress[sourceIpAddress]++; //increment the number of SYN then RST packets 

                            Logs.WriteMessageToLog("SYN Then RST Packets Recieved at " + tcpPacket.PcapHeader.Date.ToString());//log current activity
                            _lastHalfOpenConnectionByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                            if (_numberOfSynThenRstPacketsByIpAddress[sourceIpAddress] >= _numberOfUnopenedSynPacketsToASynScan)
                            { //make it >= in case two threads prempt each other and change it to 1 more than the limit
                                _lastSynScanDetectedByIpAddress[sourceIpAddress] = tcpPacket.PcapHeader.Date;
                                _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].Clear();

                                SendAlertsAndWriteLog("SYN Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + tcpPacket.PcapHeader.Date.ToString());
                                return true;
                            }
                        }
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Checks incoming packs for the LAND attack.  A LAND attack is a TCP packet with the same source/destination port and IP address
        /// </summary>
        /// <param name="newPacket">The packet to check for a LAND Attack</param>
        /// <returns>Whether or not a LAND attack occured</returns>
        private static bool DetectLandAttack(Packet newPacket)
        {
            if (newPacket is TCPPacket)
            {
                TCPPacket tcpPacket = newPacket as TCPPacket;
                if (tcpPacket.Syn && tcpPacket.SourceAddress.ToString() == tcpPacket.DestinationAddress.ToString() 
                    && tcpPacket.DestinationPort == tcpPacket.SourcePort && _lastLandAttackDetected.Add(_timeBetweenLandAttackAlerts) < tcpPacket.PcapHeader.Date)
                {
                    _lastLandAttackDetected = tcpPacket.PcapHeader.Date;
                    SendAlertsAndWriteLog("LAND Attack detected on " + Environment.MachineName + " at " + DateTime.Now.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks incoming packs for the Smurf attack.  
        /// Smurf attack is a broadcast ping with a spoofed packet of the target
        /// </summary>
        /// <param name="newPacket">The packet to check for a Smurf Attack</param>
        /// <returns>Whether of not a Smurf attack has occured</returns>
        private static bool DetectSmurfAttack(Packet newPacket)
        {
            if (newPacket is ICMPPacket && ((ICMPPacket)newPacket).MessageType == 0)
            {//if the packet is an ICMP reply
                ICMPPacket icmpPacket = newPacket as ICMPPacket;
                string sourceIpAddress = icmpPacket.SourceAddress.ToString();
                DateTime packetArrival = icmpPacket.PcapHeader.Date;

                if (_lastPingReplyTimeByIpAddress.ContainsKey(sourceIpAddress))
                    _lastPingReplyTimeByIpAddress[sourceIpAddress] = packetArrival; //add or update the entry in the ping reply dictionary
                else
                    _lastPingReplyTimeByIpAddress.Add(sourceIpAddress, packetArrival);

                #region Remoe Old Ping Replies

                List<string> sourceIpAddressesToRemove = new List<string>();
                foreach (KeyValuePair<string, DateTime> pingReply in _lastPingReplyTimeByIpAddress)
                {
                    if (packetArrival.Subtract(pingReply.Value) > _timeBeforeResetingPingReplies)
                        sourceIpAddressesToRemove.Add(pingReply.Key);
                }

                foreach (string sourceIpAddressToRemove in sourceIpAddressesToRemove)
                    _lastPingReplyTimeByIpAddress.Remove(sourceIpAddressToRemove);

                #endregion

                if (_lastPingReplyTimeByIpAddress.Count >= _numberOFPingRepliesToASmurfAttack //if the number of different IP address of pings replies is greater than the limit
                    && _lastSmurfAttackDetected.Add(_timeBetweenSmurfAlerts) < packetArrival) //and we have not alerted in the past _timeBetweenSmurfAlerts
                {                                                                             //then send an alert
                    _lastSmurfAttackDetected = packetArrival;
                    SendAlertsAndWriteLog("Possible Smurf Attack detected on " + Environment.MachineName + " at " + DateTime.Now.ToString());
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Checks incoming packets for the Ping Of Death attack.
        /// The Ping Of Death is an ICMP packet with a size of greater than 65,536 bytes
        /// </summary>
        /// <param name="newPacket">The packet to check for a Ping of Death Attack</param>
        /// <returns>Whether of not a Ping of Death attack has occured</returns>
        private static bool DetectPingOfDeathAttack(Packet newPacket)
        {
            if (newPacket is ICMPPacket && ((ICMPPacket)newPacket).Data.Length >= 65536)//if it's a ping packet with more than 2^16 bytes
            {
                ICMPPacket pingPacket = newPacket as ICMPPacket;
                string sourceAddress = pingPacket.SourceAddress.ToString(); //get the source address
                DateTime packetArrival = pingPacket.PcapHeader.Date; //get the packet arrival time

                if (!_lastPingOfDeathAlertByIpAddress.ContainsKey(sourceAddress) || //if there was no previous ping of death's or the previous one was too long ago
                    _lastPingOfDeathAlertByIpAddress[sourceAddress].Add(_timeBetweenPingOfDeathAlerts) < packetArrival)
                {
                    if (!_lastPingOfDeathAlertByIpAddress.ContainsKey(sourceAddress)) //update the last ping of death alert sent time for this source IP address
                        _lastPingOfDeathAlertByIpAddress.Add(sourceAddress,packetArrival);
                    else
                        _lastPingOfDeathAlertByIpAddress[sourceAddress] = packetArrival;

                    SendAlertsAndWriteLog("Ping of Death Attack Detected on " + Environment.MachineName + " at " + packetArrival.ToString() + " by " + sourceAddress);
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Detects a DNS Amplification attack to the machine.  
        /// A DNS Amplification attack is when an attack sends a spoofed DNS query to a DNS Server.  The reply can be serveral orders larger, thus the victim machine 
        /// can become overwhelmed.
        /// </summary>
        /// <param name="newPacket">The packet to check for a DNS Amplification Attack</param>
        /// <returns>Whether a DNS Amplification Attack has occured</returns>
        private static bool DetectDnsAmplificationAttack(Packet newPacket)
        {
            if (newPacket is UDPPacket)
            {
                UDPPacket udpPacket = newPacket as UDPPacket;
                if (udpPacket.DestinationAddress.ToString() == Program.IpAddress && udpPacket.SourcePort == 53)
                {//if it's coming to our IP Address from port 53 on another computer, then it's a DNS packet
                    DateTime packetArrival = udpPacket.PcapHeader.Date;
                    _dnsReplyTimes.Add(packetArrival);

                    #region Remoe Old DNS Replies

                    List<DateTime> timesToRemove = new List<DateTime>();
                    foreach (DateTime dnsReply in _dnsReplyTimes)
                    {
                        if (packetArrival.Subtract(dnsReply) > _timeLimitOnDnsReplies)
                            timesToRemove.Add(dnsReply);
                    }

                    foreach (DateTime timeToRemove in timesToRemove)
                        _dnsReplyTimes.Remove(timeToRemove);

                    #endregion

                    if (_dnsReplyTimes.Count >= _numberOfDnsRepliesToADnsAmplificationAttack //if the number of different IP address of pings replies is greater than the limit
                        && _lastDnsAmplificationAttackDetected.Add(_timeBetweenDnsAmplificationAlerts) < packetArrival) //and we have not alerted in the past _timeBetweenSmurfAlerts
                    {                                                                             //then send an alert
                        _lastDnsAmplificationAttackDetected = packetArrival;
                        SendAlertsAndWriteLog("Possible DNS Amplification Attack detected on " + Environment.MachineName + " at " + DateTime.Now.ToString());
                        return true;
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Goes through the list of known Attack Signatures from the XML file and looks for attacks
        /// </summary>
        /// <param name="newTcpPacket">The packet to check for known attack signatures</param>
        /// <returns>Whether or not an attack occured</returns>
        private static bool DetectAttackSignature(Packet newPacket)
        {
            bool attackDetected = false;//holds whether any attack was detected
            foreach (AttackSignature attackToDetect in _attacksToDetect)//look for any of the attacks we have signatures for
                attackDetected = attackToDetect.DetectTcpAttack(newPacket) || attackDetected;
            return attackDetected;
        }

        /// <summary>
        /// Sends an Alert, Writes to the logfile, and writes to the console the passed in string.  Useful for when an attack is detected
        /// </summary>
        /// <param name="message">The string to write to the logfile, console, and send the alert with</param>
        private static void SendAlertsAndWriteLog(string message)
        {
            Logs.WriteMessageToLog(message);
            Notifications.SendEmail(message);
            Console.WriteLine(message);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The event handler when the network device recieves a packet, checks the new packet for different attack types
        /// </summary>
        /// <param name="sender">The object that raised this event</param>
        /// <param name="packet">The packet recieved from the network device</param>
        public static void PacketArrived_EventHandler(object sender, Packet packet)
        {
            //Detect Scans
            DetectXmasScan(packet);
            DetectSynScan(packet);
            DetectNullScan(packet);
            DetectFinScan(packet);
            DetectMaimonScan(packet);

            //Detect DOS Attacks
            DetectLandAttack(packet);
            DetectSmurfAttack(packet);
            DetectPingOfDeathAttack(packet);
            DetectDnsAmplificationAttack(packet);

            //Detect Attack Signatures from XML
            DetectAttackSignature(packet);
        }

        /// <summary>
        /// Looks for unanswered SYN packets that could indicate a SYN scan
        /// If attempting to detect a SYN scan, this must be called at least every _timeBeforeClosingHalfOpenConnection
        /// </summary>
        /// <returns>Whether or not a SYN scan has been detected</returns>
        public static bool LookForUnansweredSynConnections()
        {
            lock (_synHalfOpenConnectionsByPortAndIpAddress)//ensure that the main thread has exclusive access to this Dictionary
            {
                for (int j = 0; j < _synHalfOpenConnectionsByPortAndIpAddress.Count; j++)
                {//walk through the different half open connections by IP Address
                    string sourceIpAddress = _synHalfOpenConnectionsByPortAndIpAddress.Keys.ElementAt(j);

                    if (DateTime.Now > _lastHalfOpenConnectionByIpAddress[sourceIpAddress].Add(_timeBeforeResetingSynScanCounter))//if it's been 
                        _numberOfSynThenRstPacketsByIpAddress[sourceIpAddress] = 0; // too long since the last half open connection, reset the counter

                    for (int i = 0; i < _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].Count; i++)
                    {//walk through the different half open connections by port number
                        
                        int destinationPortNumber = _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].Keys.ElementAt(i);
                        Packet halfOpenedConnection = null;
                        if (_synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].ContainsKey(destinationPortNumber))
                            halfOpenedConnection = _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber];
                        //if the connetion has been opened by a SYN/ACK or closed by a RST packet, then the Packet should be null

                        if (halfOpenedConnection != null && DateTime.Now.Subtract(halfOpenedConnection.PcapHeader.Date) > _timeBeforeClosingHalfOpenConnection)
                        {//if the connection is half open and it's been that way for too long, consider it a scan and add that to the count
                            _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress][destinationPortNumber] = null; //record the closed connection

                            if (DateTime.Now > _lastSynScanDetectedByIpAddress[sourceIpAddress].Add(_waitTimeBeforeANewSynScan))
                            {//only count the new half open connection as a scan if it's not part of the last scan
                                _numberOfSynThenRstPacketsByIpAddress[sourceIpAddress]++; //increment the number of SYN then RST packets 

                                Logs.WriteMessageToLog("SYN without SYN/ACK detected at " + DateTime.Now.ToString());//log current activity
                                _lastHalfOpenConnectionByIpAddress[sourceIpAddress] = DateTime.Now;
                                if (_numberOfSynThenRstPacketsByIpAddress[sourceIpAddress] >= _numberOfUnopenedSynPacketsToASynScan)
                                { //make it >= in case two threads prempt each other and change it to 1 more than the limit
                                    _lastSynScanDetectedByIpAddress[sourceIpAddress] = DateTime.Now;
                                    _synHalfOpenConnectionsByPortAndIpAddress[sourceIpAddress].Clear();

                                    string notificationMessage = "SYN Scan Detected On " + Environment.MachineName + " by " + sourceIpAddress + " with last packet recieved at " + halfOpenedConnection.PcapHeader.Date.ToString();
                                    Logs.WriteMessageToLog(notificationMessage);//write to the logs
                                    Notifications.SendEmail(notificationMessage);//send a notification
                                    Console.WriteLine(notificationMessage);

                                    return true;
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }

        /// <summary>
        /// Loads the attack signatures from an xml document
        /// </summary>
        /// <param name="filename">The XML document to load the attack signatures from</param>
        /// <returns>Wherther or not the signature file was successfully loaded</returns>
        public static bool LoadAttackSignatures(string filename)
        {
            XmlDocument signaturesFile = new XmlDocument();

            try//encode inside of a try catch, in case the file is improperly formatted
            {
                signaturesFile.Load(filename);//load the xml document
                XmlNode currentSignature = signaturesFile.DocumentElement.FirstChild; //get the first signature node
                while (currentSignature != null)
                {
                    _attacksToDetect.Add(new AttackSignature(currentSignature));//load the attack signature from the Xml Node
                    currentSignature = currentSignature.NextSibling;//move to the next node
                }
            }
            catch (Exception except)
            {//Catch any parsing errors from an improperly formated Signature Xml File
                Console.WriteLine("Inproperly formatted Signature File or no Signature file found:\n  " + except.Message);
                return false;
            }
            return true;
        }

        #endregion
    }
}
